#ifndef MOCHA_DETAIL_ENUM_STRING_HPP_INCLUDED
#define MOCHA_DETAIL_ENUM_STRING_HPP_INCLUDED

/**
 * @file Provides a mechanism for stringizing an enumeration. Must be used at global scope.
 */

#include <cassert>
#include <map>
#include <stdexcept>
#include <string>
#include <boost/lexical_cast.hpp>
#include <boost/noncopyable.hpp>
#include <boost/static_assert.hpp>

//enum foo {
//	bar,
//	baz
//};
//MOCHA_ENUM_STRING_BEGIN(foo) {
//	MOCHA_ENUM_STRING(bar);
//	MOCHA_ENUM_STRING(baz);
//	MOCHA_ENUM_ALIAS(baz, "qux");
//} MOCHA_ENUM_STRING_END;
//foo e = mocha_enum_string<foo>::to_enum("bar");
//std::string s = mocha_enum_string<foo>::to_string(baz);
//assert(mocha_enum_string<foo>::to_enum("baz") == mocha_enum_string<foo>::to_enum("qux"));

/**
 * Begins an enumeration stringizer.
 * @param e Name of the enumeration to stringize. Must be the fully qualified name.
 * @see MOCHA_ENUM_STRING_END
 */
#define MOCHA_ENUM_STRING_BEGIN(e) template<> class mocha_enum_string< e > : public mocha_enum_string_base<mocha_enum_string< e >, e > { public: static void insert_all()
/**
 * Adds an enumerator to a stringized enumeration.
 * @param e Name of the enumerator to stringize. Must be the fully qualified name.
 * @see MOCHA_ENUM_STRING_BEGIN
 * @see MOCHA_ENUM_STRING_END
 */
#define MOCHA_ENUM_STRING(e) insert( #e , e )
/**
 * Adds an arbitrary alias for an enumerator to a stringized enumeration.
 * @param e Name of the enumerator to alias. Must be the fully qualified name.
 * @param alias Alias for the enumerator.
 * @see MOCHA_ENUM_STRING_BEGIN
 * @see MOCHA_ENUM_STRING_END
 */
#define MOCHA_ENUM_ALIAS(e, alias) insert( alias , e )
/**
 * Ends an enumeration stringizer.
 * Follows any stringizer declarations and ends the mapping.
 * @see MOCHA_ENUM_STRING_END
 */
#define MOCHA_ENUM_STRING_END }

namespace mocha {
/**
 * Enumeration stringizer exception.
 * Indicates a bad enumeration string mapping.
 * Thrown when a key does not exist or an ambiguous enumerator is encountered.
 */
class bad_enum_string : public std::runtime_error {
public:
	bad_enum_string(): std::runtime_error("") {}
	/**
	 * @param what
	 */
	explicit bad_enum_string(const std::string& what): std::runtime_error(what) {}
};
}
/**
 * Base for enumeration stringizers.
 * Must be at global scope (not within a namespace) to avoid specialization errors.
 * @param T 
 * @param E Enumeration type.
 * @see mocha_enum_string
 * @see MOCHA_ENUM_STRING
 * @see MOCHA_ENUM_ALIAS
 */
template<typename T, typename E>
class mocha_enum_string_base : public boost::noncopyable {
public:
	typedef E enum_type; // By using a built-in integer type instead, enumerators from any enumeration could be inserted into the mapping of any enumeration.
	typedef std::string string_type;
protected:
	typedef std::map<string_type, enum_type> enum_con_t;
	/**
	 * Inserts a mapping.
	 * @param s Enumerator string.
	 * @param e Enumerator.
	 */
	static void insert(const string_type& s, enum_type e) {
		enum_mapping().insert(typename enum_con_t::value_type(s, e));
	}
public:
	/**
	 * Maps from an enumerator to a string.
	 * Throws an exception if the enumerator is not found or is ambiguous (multiple enumerators have the same value).
	 * @param e Enumerator.
	 * @return Enumerator string.
	 */
	static const string_type& to_string(enum_type e) {
		typename enum_con_t::const_iterator i, j;
		for (i = enum_mapping().begin(); enum_mapping().end() != i; ++i) {
			if (e == i->second) {
				break;
			}
		}
		if (enum_mapping().end() == i) {
			throw mocha::bad_enum_string(boost::lexical_cast<string_type>(e)); // No such enumerator exists.
		}
		j = i;
		for (++j; enum_mapping().end() != j; ++j) {
			if (e == j->second) {
				break;
			}
		}
		if (enum_mapping().end() != j) {
			throw mocha::bad_enum_string(boost::lexical_cast<string_type>(e)); // More than one enumerator with this value exists.
		}
		return i->first;
	}
	/**
	 * Maps from a string to an enumerator.
	 * Throws an exception if the string is not found.
	 * @param s Enumerator string.
	 * @return Enumerator.
	 */
	static enum_type to_enum(const string_type& s) {
		typename enum_con_t::const_iterator i = enum_mapping().find(s);
		if (enum_mapping().end() == i) {
			throw mocha::bad_enum_string(s);
		}
		return i->second;
	}
private:
	/**
	 * Gets the map that stores the string/enumerator pairs.
	 * On first call, the map is initialized and populated.
	 * @return Enumerator map.
	 */
	static enum_con_t& enum_mapping() {
		static enum_con_t enum_map;
		static bool init = false;
		if (!init) {
			init = true; // Note that this flag must be set before the call to insert_all, or an infinite recursion occurs within this function!
			T::insert_all();
			assert(!enum_map.empty()); // TODO: This indicates an empty mapping. Is this really an error? Note that completing the mapping later is not possible using the helper macros.
		}
		return enum_map;
	}
};
/**
 * Basic stringizer. Inserts strings into the mapping.
 * Specializations must implement the enumeration stringizer concept.
 * Must be at global scope (not within a namespace) to avoid specialization errors.
 * @param E Enumeration type.
 * @see MOCHA_ENUM_STRING_BEGIN
 */
template<typename E>
class mocha_enum_string : public mocha_enum_string_base<mocha_enum_string<E>, E> {
BOOST_STATIC_ASSERT(sizeof(E) == 0); // Only a specialization can be instantiated.
public:
	/**
	 * A member function with this name and signature must exist in specializations.
	 * @see insert
	 */
	static void insert_all() {}
};
namespace mocha {
	using ::mocha_enum_string;
}

#endif

